﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using CoreEx.Common.Debug;
using CoreEx.Services;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.Common.Extensions
{
    public static class TypeExtensions
    {
        private static IServiceContainer _serviceContainer;


        static TypeExtensions()
        {
            _serviceContainer = ServiceLocator.Container;
        }

        private static Type FindIEnumerable(Type seqType)
        {
            if (seqType == null || seqType == typeof(string))
                return null;
            if (seqType.IsArray)
                return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType());
            if (seqType.IsGenericType)
            {
                foreach (Type arg in seqType.GetGenericArguments())
                {
                    Type ienum = typeof(IEnumerable<>).MakeGenericType(arg);
                    if (ienum.IsAssignableFrom(seqType))
                        return ienum;
                }
            }
            Type[] ifaces = seqType.GetInterfaces();
            if (ifaces != null && ifaces.Length > 0)
            {
                foreach (Type iface in ifaces)
                {
                    Type ienum = FindIEnumerable(iface);
                    if (ienum != null)
                        return ienum;
                }
            }
            if (seqType.BaseType != null && seqType.BaseType != typeof(object))
                return FindIEnumerable(seqType.BaseType);
            return null;
        }


        public static Type GetGenericElementType(this Type type)
        {
            Type ienum = FindIEnumerable(type);
            if (ienum == null)
                return type;
            return ienum.GetGenericArguments()[0];
        }


        public static bool IsEnumerable(this Type type )
        {
            return (typeof (IEnumerable)).IsAssignableFrom(type) && type != typeof(string);
        }

        public static bool IsSystemType(this Type type)
        {
            if (string.IsNullOrEmpty(type.Namespace))
                return true;
            return type.GetGenericElementType().Namespace.StartsWith("System");
        }

        public static void Dump(this Type type)
        {
            ITypeDumper typeDumper = _serviceContainer.GetService<ITypeDumper>();
            typeDumper.Dump(type);
        }

        /// <summary>
        /// Returns the default value for any <see cref="Type"/>.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> for which to return the default value.</param>
        /// <returns></returns>
        public static object GetDefaultValue(this Type type)
        {
            if (type.IsValueType)
                return Activator.CreateInstance(type);
            return null;
        }

        /// <summary>
        /// Determines if the <paramref name="type"/> is wrapped by a <see cref="Nullable{T}"/>.
        /// </summary>
        /// <param name="type">The target type</param>
        /// <returns>Returns <b>True</b> if the <paramref name="type"/> is wrapped by a <see cref="Nullable{T}"/>, otherwise <b>False.</b></returns>
        public static bool IsNullableType(this Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
        }


        /// <summary>
        /// Gets the underlying non-nullable <see cref="Type"/> from a <see cref="Nullable{T}"/>.
        /// </summary>
        /// <param name="type">The target type.</param>
        /// <returns>
        /// If the <paramref name="type"/> is wrapped by a <see cref="Nullable{T}"/> , the underlying <see cref="Type"/> is returned.
        /// Otherwise the <paramref name="type"/> itself is returned.
        /// </returns>
        public static Type GetNonNullableType(this Type type)
        {
            return IsNullableType(type) ? type.GetGenericArguments()[0] : type;
        }

        /// <summary>
        /// Gets a list of properties, one for each path element in the given <paramref name="path"/>  
        /// </summary>
        /// <param name="type">The root type for which to start searching fro the first path element.</param>
        /// <param name="path">The property path</param>
        /// <returns>A list of <see cref="PropertyInfo"/> instances 
        /// where the first occurrence reflects the  property found by the first path element.</returns>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if the path element is not found in the target type.</exception>
        public static PropertyInfo[] GetPropertiesFromPath(this Type type, string path)
        {
            var propertyList = new List<PropertyInfo>();
            var pathElements = path.Split('.');
            var targetType = type;
            foreach (var pathElement in pathElements)
            {
                var propertyInfo = targetType.GetProperty(pathElement);
                if (propertyInfo == null)
                    throw new ArgumentOutOfRangeException("path",path,string.Format("The path element '{0}' was not found in type : {1}", pathElement,type.Name));
                propertyList.Add(propertyInfo);
                targetType = propertyInfo.PropertyType.GetGenericElementType();
            }
            return propertyList.ToArray();
        }



    }
}
